package pe.com.rimac.portal.util.url.impl;

import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;

import pe.com.rimac.portal.util.url.UrlService;
import pe.com.rimac.portal.util.url.UrlType;

import com.ibm.portal.Disposable;
import com.ibm.portal.Locator;
import com.ibm.portal.ModelException;
import com.ibm.portal.ObjectID;
import com.ibm.portal.content.ContentModel;
import com.ibm.portal.content.ContentPage;
import com.ibm.portal.content.ControlLocator;
import com.ibm.portal.content.LayoutControl;
import com.ibm.portal.content.LayoutModel;
import com.ibm.portal.content.LayoutNode;
import com.ibm.portal.portlet.service.model.ContentModelProvider;
import com.ibm.portal.portlet.service.model.PortletModelProvider;
import com.ibm.portal.portletmodel.PortletModel;
import com.ibm.portal.portletmodel.PortletWindow;
import com.ibm.portal.state.EngineURL;
import com.ibm.portal.state.PortletStateManager;
import com.ibm.portal.state.StateHolderController;
import com.ibm.portal.state.URLFactory;
import com.ibm.portal.state.accessors.portlet.PortletAccessorController;
import com.ibm.portal.state.accessors.portlet.PortletAccessorFactory;
import com.ibm.portal.state.accessors.portlet.PortletTargetAccessorController;
import com.ibm.portal.state.accessors.selection.SelectionAccessorController;
import com.ibm.portal.state.accessors.selection.SelectionAccessorFactory;
import com.ibm.portal.state.exceptions.CannotInstantiateAccessorException;
import com.ibm.portal.state.exceptions.StateException;
import com.ibm.portal.state.exceptions.UnknownAccessorTypeException;
import com.ibm.portal.state.service.PortletStateManagerService;

public class UrlServiceImpl implements UrlService 
{
    private static final Logger logger = Logger.getLogger(UrlServiceImpl.class.getName());
    
    private PortletStateManagerService stateService;
    private PortletModelProvider portletModelProvider;
    private ContentModelProvider contentModelProvider;
    
    public UrlServiceImpl()
    {
        stateService = ServiceLocator.getInstance().getPortletStateManagerService();
        portletModelProvider = ServiceLocator.getInstance().getPortletModelProvider();
        contentModelProvider = ServiceLocator.getInstance().getContentModelProvider();        
    }
    
    /**
     * Creates a new Single Portlet Refresh render URL.
     */
    public EngineURL createSingleRenderURL(PortletRequest request, PortletResponse response, Map renderParams)
        throws PortletException
    {
        return createSingleURL(request, response, renderParams, UrlType.RENDER);
    }
    
    /**
     * Creates a new Single Portlet Refresh action URL.
     */
    public EngineURL createSingleActionURL(PortletRequest request, PortletResponse response, Map actionParams)
        throws PortletException
    {
        return createSingleURL(request, response, actionParams, UrlType.ACTION);
    }
    
    public EngineURL createSingleURL(PortletRequest request, PortletResponse response, Map params, UrlType type)
        throws PortletException
    {
        PortletStateManager stateManager = null;
        URLFactory urlFactory = null;
        try
        {
            // get the request-specific portlet state manager
            stateManager = stateService.getPortletStateManager(request, response);
            // get the portlet window
            PortletWindow portletWindow = portletModelProvider.getCurrentPortletWindow(request);
            // get the URL factory
            urlFactory = stateManager.getURLFactory();
            // get a new EngineURL object from the factory
            EngineURL url = urlFactory.newURL(null);
            // get the URL-specific state
            StateHolderController state = url.getState();
            // make the URL a single portlet refresh URL
            setSelectionMapping(stateManager, portletWindow, state);
            
            if (UrlType.ACTION.equals(type))
            {
                // set the portlet action
                setAction(stateManager, portletWindow, state);
            }
                    
            // set the render state
            setState(stateManager, state, params);
            
            return url;
        }
        catch (StateException e) 
        {
            throw new PortletException(e);
        } 
        catch (ModelException e) 
        {
            throw new PortletException(e);
        }        
        finally
        {
            dispose(urlFactory);
            dispose(stateManager);
        }
    }
    
    public EngineURL createSingleRenderURL(PortletRequest request, PortletResponse response, String pageUniqueName, String layoutUniqueName, Map actionParams)
        throws PortletException
    {
        return createSingleURL(request, response, pageUniqueName, layoutUniqueName, actionParams, UrlType.RENDER);
    }
    
    public EngineURL createSingleActionURL(PortletRequest request, PortletResponse response, String pageUniqueName, String layoutUniqueName, Map actionParams)
        throws PortletException
    {
        return createSingleURL(request, response, pageUniqueName, layoutUniqueName, actionParams, UrlType.ACTION);
    }
    
    public EngineURL createSingleURL(PortletRequest request, PortletResponse response, String pageUniqueName, String layoutUniqueName, Map params, UrlType type)
        throws PortletException
    {
        PortletStateManager stateManager = null;
        URLFactory urlFactory = null;
        try
        {
            // get the request-specific portlet state manager
            stateManager = stateService.getPortletStateManager(request, response);
            // get the portlet window
            ContentPage page = getPage(request, response, pageUniqueName);
            LayoutControl layoutControl = getLayoutControl(request, response, page, layoutUniqueName);
            PortletWindow portletWindow = getPortletWindow(request, response, layoutControl);
            // get the URL factory
            urlFactory = stateManager.getURLFactory();
            // get a new EngineURL object from the factory
            EngineURL url = urlFactory.newURL(null);
            // get the URL-specific state
            StateHolderController state = url.getState();
            // make the URL a single portlet refresh URL
            setSelectionMapping(stateManager, portletWindow, state, page);
            if (UrlType.ACTION.equals(type))
            {
                // set the portlet action
                setAction(stateManager, portletWindow, state);
            }
            // set the action parameters
            setState(stateManager, state, params, layoutControl);
            
            return url;
        }
        catch (StateException e) 
        {
            throw new PortletException(e);
        } 
        finally
        {
            dispose(urlFactory);
            dispose(stateManager);
        }
    }
    
    public EngineURL createRenderURL(PortletRequest request, PortletResponse response, ObjectID pageOID, ObjectID layoutOID, Map actionParams)
        throws PortletException
    {
        return createURL(request, response, pageOID, layoutOID, actionParams, UrlType.RENDER);
    }
    
    public EngineURL createRenderURL(PortletRequest request, PortletResponse response, String pageUniqueName, String layoutUniqueName, Map actionParams)
        throws PortletException
    {
        return createURL(request, response, pageUniqueName, layoutUniqueName, actionParams, UrlType.RENDER);
    }
    
    public EngineURL createActionURL(PortletRequest request, PortletResponse response, ObjectID pageOID, ObjectID layoutOID, Map actionParams)
        throws PortletException
    {
        return createURL(request, response, pageOID, layoutOID, actionParams, UrlType.ACTION);
    }
    
    public EngineURL createActionURL(PortletRequest request, PortletResponse response, String pageUniqueName, String layoutUniqueName, Map actionParams)
        throws PortletException
    {
        return createURL(request, response, pageUniqueName, layoutUniqueName, actionParams, UrlType.ACTION);
    }
    
    public EngineURL createURL(PortletRequest request, PortletResponse response, ObjectID pageOID,
            ObjectID layoutOID, Map params, UrlType type) throws PortletException
    {
        PortletStateManager stateManager = null;
        // get the needed factories
        URLFactory urlFactory = null;
        try
        {
            stateManager = stateService.getPortletStateManager(request, response);
            urlFactory = stateManager.getURLFactory();
            
            ContentPage page = getPage(request, response, pageOID);
            LayoutControl layoutControl = getLayoutControl(request, response, page, layoutOID);
            PortletWindow portletWindow = getPortletWindow(request, response, layoutControl);
            
            // get a new URL from the URL factory
            // null indicates that the current state should be copied
            EngineURL url = urlFactory.newURL(null);
            // set the action target
            StateHolderController urlState = url.getState();
            // map page to url
            setSelectionMapping(stateManager, urlState, page);
            
            if (UrlType.ACTION.equals(type))
            {
                // set the portlet action
                setAction(stateManager, portletWindow, urlState);
            }

            setState(stateManager, urlState, params, layoutControl);
            return url;
        }
        catch (StateException e)
        {
            throw new PortletException(e);
        }
        finally 
        {
            dispose(urlFactory);
            dispose(stateManager);
        }
    }
    
    public EngineURL createURL(PortletRequest request, PortletResponse response, String pageUniqueName,
            String layoutUniqueName, Map params, UrlType type) throws PortletException
    {
        PortletStateManager stateManager = null;
        // get the needed factories
        URLFactory urlFactory = null;
        try
        {
            stateManager = stateService.getPortletStateManager(request, response);
            urlFactory = stateManager.getURLFactory();
            
            ContentPage page = getPage(request, response, pageUniqueName);
            LayoutControl layoutControl = getLayoutControl(request, response, page, layoutUniqueName);
            PortletWindow portletWindow = getPortletWindow(request, response, layoutControl);
            
            // get a new URL from the URL factory
            // null indicates that the current state should be copied
            EngineURL url = urlFactory.newURL(null);
            // set the action target
            StateHolderController urlState = url.getState();
            // map page to url
            setSelectionMapping(stateManager, urlState, page);
            
            if (UrlType.ACTION.equals(type))
            {
                // set the portlet action
                setAction(stateManager, portletWindow, urlState);
            }

            setState(stateManager, urlState, params, layoutControl);
            return url;
        }
        catch (StateException e)
        {
            throw new PortletException(e);
        }
        finally 
        {
            dispose(urlFactory);
            dispose(stateManager);
        }
    }
    
    public EngineURL createPageUrl(PortletRequest request, PortletResponse response, String pageUniqueName) throws PortletException
    {
        ObjectID pageID = getPage(request, response, pageUniqueName).getObjectID();
        return createPageUrl(request, response, pageID);
    }
    
    public EngineURL createPageUrl(PortletRequest request, PortletResponse response, ObjectID pageID) throws PortletException
    {
        PortletStateManager stateManager = null;
        URLFactory urlFactory = null;
        SelectionAccessorController selectionCtrl = null;
        try
        {
            stateManager = stateService.getPortletStateManager(request, response);
            urlFactory = stateManager.getURLFactory();
            // get a EngineURL from the factory; maintain navigational state
            EngineURL url = urlFactory.newURL(null);
            SelectionAccessorFactory selectionFct = (SelectionAccessorFactory) stateManager.getAccessorFactory(SelectionAccessorFactory.class);
            // get a selection controller that operates on the URL-specific state
            selectionCtrl = selectionFct.getSelectionAccessorController(url.getState());
            // modify page selection and return URL
            selectionCtrl.setSelection(pageID);
            return url;
        }
        catch (StateException e) 
        {
            throw new PortletException(e);
        }
        finally 
        {
            dispose(selectionCtrl);
            dispose(urlFactory);
            dispose(stateManager);
        }
    }
    
    public EngineURL createPageUrl(PortletRequest request, PortletResponse response, String pageUniqueName, String layoutUniqueName, String type, Map params) throws PortletException
    {
        ContentPage page = getPage(request, response, pageUniqueName);
        LayoutControl layout = getLayoutControl(request, response, page, layoutUniqueName);
        return createPageUrl(request, response, page.getObjectID(), layout.getObjectID(), type, params);    
    }
    
    public EngineURL createPageUrl(PortletRequest request, PortletResponse response, ObjectID pageOID, ObjectID layoutOID, String type, Map params) throws PortletException
    {
        PortletStateManager stateManager = null;
        URLFactory urlFactory = null;
        SelectionAccessorController selectionCtrl = null;
        try
        {
            stateManager = stateService.getPortletStateManager(request, response);
            urlFactory = stateManager.getURLFactory();
            // get a EngineURL from the factory; maintain navigational state
            EngineURL url = urlFactory.newURL(null);
            SelectionAccessorFactory selectionFct = (SelectionAccessorFactory) stateManager.getAccessorFactory(SelectionAccessorFactory.class);
            // get a selection controller that operates on the URL-specific state
            selectionCtrl = selectionFct.getSelectionAccessorController(url.getState());
            // modify page selection and return URL
            selectionCtrl.setSelection(pageOID);
            
            // Now pass paramaters to specific portlet
            // and invoke specific action
            
            // get the portlet window
            ContentPage page = getPage(request, response, pageOID);
            LayoutControl layoutControl = getLayoutControl(request, response, page, layoutOID);
            PortletWindow portletWindow = getPortletWindow(request, response, layoutControl);
            StateHolderController state = url.getState();
            if ("ACTION".equals(type))
            {
                // set the portlet action
                setAction(stateManager, portletWindow, state);
            }
            // set the action parameters
            setState(stateManager, state, params, layoutControl);
            
            return url;
        }
        catch (StateException e) 
        {
            throw new PortletException(e);
        }
        finally 
        {
            dispose(selectionCtrl);
            dispose(urlFactory);
            dispose(stateManager);
        }
    }
    
    /**
     * Helper method which maps the currently selected page to the given portlet window.
     * Typically this method will operate on a URL-specific state holder. 
     */
    private void setSelectionMapping(PortletStateManager stateManager, PortletWindow window,
            StateHolderController urlState) throws StateException
    {
        SelectionAccessorFactory factory = (SelectionAccessorFactory) stateManager
            .getAccessorFactory(SelectionAccessorFactory.class);
        SelectionAccessorController selCtrl = factory.getSelectionAccessorController(urlState);
        try
        {
            selCtrl.setSelectionMapping(selCtrl.getSelection(), window.getObjectID());
        }
        finally 
        {
            dispose(selCtrl);
        }
    }
    
    /**
     * Helper method which maps the given page to the given portlet window.
     * Typically this method will operate on a URL-specific state holder. 
     */
    private void setSelectionMapping(PortletStateManager stateManager, PortletWindow window,
            StateHolderController urlState, ContentPage page) throws StateException
    {
        SelectionAccessorFactory factory = (SelectionAccessorFactory) stateManager
            .getAccessorFactory(SelectionAccessorFactory.class);
        SelectionAccessorController selCtrl = factory.getSelectionAccessorController(urlState);
        try
        {
            selCtrl.setSelection(page.getObjectID());
            selCtrl.setSelectionMapping(selCtrl.getSelection(), window.getObjectID());
        }
        finally 
        {
            dispose(selCtrl);
        }
    }  
    
    /**
     * Helper method which maps the given page.
     */
    private void setSelectionMapping(PortletStateManager stateManager, StateHolderController urlState,
            ContentPage page) throws StateException
    {
        SelectionAccessorFactory factory = (SelectionAccessorFactory) stateManager
            .getAccessorFactory(SelectionAccessorFactory.class);
        SelectionAccessorController selCtrl = factory.getSelectionAccessorController(urlState);
        try
        {
            selCtrl.setSelection(page.getObjectID());
        }
        finally 
        {
            dispose(selCtrl);
        }
    }
    
    /**
     * Helper method which sets an action into the given URL-specific state. 
     */
    private void setState(PortletStateManager stateManager, StateHolderController urlState, Map params)
        throws StateException
    {
        // get the portlet accessor controller
        PortletAccessorController portletCtrl = stateManager.getPortletAccessorController(urlState);
        
        setState(portletCtrl, params);
    }
    
    private void setState(PortletStateManager stateManager, StateHolderController urlState, Map params, LayoutNode layoutNode) 
        throws UnknownAccessorTypeException, CannotInstantiateAccessorException
    {
        // get the portlet accessor controller        
        PortletAccessorFactory accessorFactory = (PortletAccessorFactory)stateManager.getAccessorFactory(PortletAccessorFactory.class);
        PortletAccessorController portletCtrl = accessorFactory.getPortletAccessorController(layoutNode.getObjectID(), urlState);
        
        setState(portletCtrl, params);
    }
    
    private void setState(PortletAccessorController portletCtrl, Map params)
    {
        try 
        {
            // clearing previous state.
            for (Iterator iterator = portletCtrl.getParameters().entrySet().iterator(); iterator.hasNext();)
            {
                Map.Entry entry = (Map.Entry) iterator.next();
                logger.fine("removing previous state:" + entry.getKey()+"="+entry.getValue());
                portletCtrl.getParameters().remove(entry.getKey());
            }
            
            // set render /action parameters (if any)
            if (params != null && !params.isEmpty()) 
            {
                portletCtrl.getParameters().putAll(params);
            }
        }
        finally 
        {
            dispose(portletCtrl);
        }
    }
    
    /**
     * Helper method which sets an action into the given URL-specific state. 
     */
    private void setAction(PortletStateManager stateManager, PortletWindow window, StateHolderController urlState)
        throws StateException
     {
        // get the target accessor controller to set the action
        PortletTargetAccessorController targetCtrl = stateManager.getPortletTargetAccessorController(urlState);
        try 
        {
            // set the action to our portlet window
            targetCtrl.setActionTarget(window.getObjectID());
        }
        finally 
        {
            dispose(targetCtrl);
        }
    }
    
    private ContentPage getPage(PortletRequest request, PortletResponse response, String pageUniqueName)
        throws PortletException
    {
        try
        {
            ContentModel contentModel = contentModelProvider.getContentModel(request, response);
            Locator locator = contentModel.getLocator();
            ContentPage page = (ContentPage) locator.findByUniqueName(pageUniqueName);
            if (page == null)
            {
                throw new PortletException("Page with unique name '" + pageUniqueName + "' not found");
            }
            return page;
        }
        catch (ModelException e)
        {
            throw new PortletException("Error looking up Page with unique name '" + pageUniqueName + "' not found", e);
        }
    }
    
    private ContentPage getPage(PortletRequest request, PortletResponse response, ObjectID pageOID)
        throws PortletException
    {
        try
        {
            ContentModel contentModel = contentModelProvider.getContentModel(request, response);
            Locator locator = contentModel.getLocator();
            ContentPage page = (ContentPage) locator.findByID(pageOID);
            if (page == null)
            {
                throw new PortletException("Page with oid '" + pageOID + "' not found");
            }
            return page;
        }
        catch (ModelException e)
        {
            throw new PortletException("Error looking up Page with oid '" + pageOID + "' not found", e);
        }
    }
    
    private LayoutControl getLayoutControl(PortletRequest request, PortletResponse response, ContentPage page,
            String layoutUniqueName)
        throws PortletException
    {
        try
        {
            ContentModel contentModel = contentModelProvider.getContentModel(request, response);
            LayoutModel LayoutModel = contentModel.getLayoutModel(page);
            ControlLocator controlLocator = (ControlLocator) LayoutModel.getLocator();
            LayoutControl layoutControl = (LayoutControl) controlLocator.findByUniqueName(layoutUniqueName);
            if (layoutControl == null)
            {
                throw new PortletException("Layout control with unique name '"+layoutUniqueName+"' not found on page '" +
                        page.getObjectID() + "'");
            }
            return layoutControl;
        }
        catch (ModelException e)
        {
            throw new PortletException("Error looking up Layout Control with unique name '" + layoutUniqueName + 
                    "' on page '" + page.getObjectID().getUniqueName() + "'", e);
        }
    }
    
    private LayoutControl getLayoutControl(PortletRequest request, PortletResponse response, ContentPage page,
            ObjectID layoutOID)
        throws PortletException
    {
        try
        {
            ContentModel contentModel = contentModelProvider.getContentModel(request, response);
            LayoutModel LayoutModel = contentModel.getLayoutModel(page);
            ControlLocator controlLocator = (ControlLocator) LayoutModel.getLocator();
            LayoutControl layoutControl = (LayoutControl) controlLocator.findByID(layoutOID);
            if (layoutControl == null)
            {
                throw new PortletException("Layout control with ObjectID '" + layoutOID + "' not found on page '" +
                        page.getObjectID().getUniqueName() + "'");
            }
            return layoutControl;
        }
        catch (ModelException e)
        {
            throw new PortletException("Error looking up Layout Control with ObjectID '" + layoutOID +
                    "' on page '" + page.getObjectID() + "'", e);
        }
    }    
    
    private PortletWindow getPortletWindow(PortletRequest request, PortletResponse response, LayoutControl layoutControl)
        throws PortletException
    {
        try
        {
            PortletModel portletModel = portletModelProvider.getPortletModel(request, response);
            PortletWindow portletWindow = portletModel.getPortletWindow(layoutControl);
            if (portletWindow == null)
            {
                throw new PortletException("Portlet for layout '" + layoutControl + "' not found");
            }
            return portletWindow;
        }
        catch (ModelException e)
        {
            throw new PortletException("Error looking up Portlet for layout '" + layoutControl + "' not found", e);
        }
    }
    
    private void dispose(Disposable disposable)
    {
        if (disposable != null)
        {
            try
            {
                disposable.dispose();
            }
            catch (Throwable t)
            {
                logger.log(Level.WARNING, "Couldnt dispose " + disposable, t);
            }
        }
    }
}
